#include <tl/expected.hpp>
#include <memory>
#include <string>
#include <iostream>
#include <cstring>

enum class ErrorCode { Success, InvalidParam, MemoryError };

std::string GetErrorMessage(ErrorCode  err_code) {
    if (err_code == ErrorCode::InvalidParam) {
        return "invaid param";
    } else if (err_code == ErrorCode::MemoryError) {
        return "out of memory";
    }
    return "success";
}
class Base {
public:
    Base() = default;
    Base(Base&& other) noexcept 
        : data_(std::exchange(other.data_, nullptr)),
          size_(std::exchange(other.size_, 0)) {}
    Base& operator=(const Base&) = delete;
    
    ErrorCode Init(int size) {
        if(size < 0) return ErrorCode::InvalidParam;
        auto data = new (std::nothrow) float[size];
        if (data == nullptr)
            return ErrorCode::MemoryError;
        size_ = size;
        data_ = data;
        for (auto idx = 0; idx < size; idx++) data[idx] = idx;
        return ErrorCode::Success;
    }
    static tl::expected<Base, ErrorCode> Create(int size) noexcept {
        Base obj;
        auto err_code = obj.Init(size);
        if (err_code != ErrorCode::Success) {
            return tl::unexpected(err_code);
        }
        return obj;
    }
    ErrorCode CloneTo(Base& dst) {
        if (dst.size() != size_) return ErrorCode::InvalidParam;
        memcpy(dst.data(), data_, size_ * sizeof(float));
        return ErrorCode::Success;
    }
    tl::expected<Base, ErrorCode> Clone() noexcept {
        Base dst;
        auto err_code = dst.Init(size_);
        if (err_code != ErrorCode::Success) {
            return tl::unexpected(err_code);
        }
        err_code = CloneTo(dst);
        if (err_code != ErrorCode::Success) {
            return tl::unexpected(err_code);
        }
        return dst;
    }

    void Destroy() noexcept {
        if (data_)
            delete[] data_;
        size_ = 0;
        data_ = nullptr;
    }

    ~Base() {
        Destroy();
    }

    float ComputeSum() {
        float sum = 0;
        for (auto idx = 0; idx < size_; idx++) {
            sum += data_[idx];
        }
        return sum;
    }
    float* data() {
        return data_;
    }
    const float* data() const {
        return data_;
    }
    void set_data(float* data) {
        data_ = data;
    }
    const int size() const {
        return size_;
    }
    void set_size(int size) {
        size_ = size;
    }

private:
    float* data_ = nullptr;
    int size_;
};

class Derived {
public:
    Derived() = default;
    ErrorCode Init(int size) {
        return base_.Init(size);
    }
    static tl::expected<Derived, ErrorCode> Create(int size) noexcept {
        Derived obj;
        auto err_code = obj.Init(size);
        if (err_code != ErrorCode::Success) {
            return tl::unexpected(err_code);
        }
        return obj;
    }
    ErrorCode CloneTo(Derived& dst) {
        return base_.CloneTo(dst.base());
    }
    tl::expected<Derived, ErrorCode> Clone() noexcept {
        Derived dst;
        auto err_code = dst.Init(base_.size());
        if (err_code != ErrorCode::Success) {
            return tl::unexpected(err_code);
        }
        err_code = CloneTo(dst);
        if (err_code != ErrorCode::Success) {
            return tl::unexpected(err_code);
        }
        return dst;
    }
    Base& base() {
        return base_;
    }
    float ComputeMean() {
        return base_.ComputeSum() / base_.size();
    }
private:
    Base base_;
};
int main() {
    auto obj = Derived::Create(10);
    if (!obj) {
        std::cerr << GetErrorMessage(obj.error()) << std::endl;
        return static_cast<int>(obj.error());
    }
    auto obj2 = obj.value().Clone();
    if (!obj2) {
        std::cerr << GetErrorMessage(obj2.error()) << std::endl;
        return static_cast<int>(obj2.error());
    }
    std::cout << obj2.value().ComputeMean() << std::endl;
    return 0;
}